﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Serialization;

namespace Limestone.Storage.XML
{
    public class XmlStorage : IStorage
    {
        private readonly IXmlStorageConfig _xmlStorageConfig;
        private IFileReader _fileReader;
        private XmlReaderSettings _xmlReadSettings;
        private FileHelpers _fileHelpers;


        public XmlStorage(IXmlStorageConfig xmlStorageConfig, IGetCurrentUserId getCurrentUserId)
        {
            _xmlStorageConfig = xmlStorageConfig;
            GetCurrentUserId = getCurrentUserId;
            _xmlReadSettings = new XmlReaderSettings
                                   {
                                       ConformanceLevel = ConformanceLevel.Fragment,
                                       IgnoreWhitespace = true,
                                       IgnoreComments = true
                                   };
            _fileReader = new FileReader(_xmlReadSettings, _xmlStorageConfig);
            _fileHelpers = new FileHelpers(_xmlStorageConfig);
            LookUpProvider = new LookUpProvider(_fileReader, _xmlStorageConfig);

            //EnsureSetupIsCorrect();
        }

        private void EnsureSetupIsCorrect()
        {
            if (GetPage("/") == null)
            {
                SavePage(new Page("Start")
                             {
                                 Name = "Start",
                                 Path = "/"
                             });
            }
        }

        public IGetCurrentUserId GetCurrentUserId { get; set; }
        public LookUpProvider LookUpProvider { get; set; }

        #region pageLookUp

        public IPageLookUp GetPageLookUp(bool forceDeepReload)
        {
            return LookUpProvider.GetPageLookUp(forceDeepReload);
        }

        public void RemovePageFromLookUp(string pagePath)
        {
            LookUpProvider.RemovePageStub(pagePath);
        }

        public void AddPageToLookUp(IPageStub stub)
        {
            LookUpProvider.AddPageStub(stub, PageHelper.GetParentPath(stub.Path));
        }

        #endregion

        #region get and save pages


        /// <summary>
        /// Loads and returns a full page object.
        /// For quick look ups, use GetPageStub instead
        /// </summary>
        /// <param name="pagePath"></param>
        /// <returns></returns>
        public IPage GetPage(string pagePath)
        {
            var filePath = _fileHelpers.GetPageFilePath(pagePath);

            if (!File.Exists(filePath))
            {
                return null;
            }

            // TODO cache this
            var page = _fileHelpers.LoadObjectFromDisc<Page>(filePath);
            
            page.Fields.ResetIndices();

            return page;
        }

        public IPage SavePage(IPage page)
        {
            var filePath = _fileHelpers.GetPageFilePath(page.Path);

            // if we are saving a new page, make sure all parents exists as folders
            if (!File.Exists(filePath))
            {
                _fileHelpers.EnsureParentDirectoryExists(Path.GetDirectoryName(filePath));
            }

            return _fileHelpers.SaveToFile<Page>(page as Page, filePath);
        }



        #endregion

        #region copy and move


        public bool MovePage(IPage page, string newParentPath)
        {
            var newParentPage = GetPage(newParentPath);

            return newParentPage != null && MovePage(page, newParentPage);
        }

        public bool MovePage(IPage page, IPage newParentPage)
        {
            var oldPath = page.Path;

            var movedPage = MovePageFile(page, newParentPage);
            if (movedPage == null)
                return false;

            movedPage.Modified = DateTime.Now;
            movedPage.ModifiedBy = GetCurrentUserId.CurrentUserId;
            SavePage(movedPage);

            // update navigation file
            LookUpProvider.MovePageStub(oldPath, newParentPage.Path);

            return true;
        }

        private IPage MovePageFile(IPage page, IPageStub newParentPage, bool autoRenameIfTargetExists = false)
        {
            // check if target is same as source
            if (page.ParentPath == newParentPage.Path || page.Path == newParentPage.Path)
                return null;

            var targetName = page.Name;

            if (!IsValidMoveTarget(page, newParentPage))
            {
                if (autoRenameIfTargetExists)
                    targetName = GetUniqueDiscName(_fileHelpers.ToDiscPath(newParentPage.Path + "/" + page.Name));
                else
                    return null;
            }

            // ensure target is a folder
            ConvertToFolder(newParentPage);

            var parentPageDiscInfo = _fileHelpers.GetPageDiscInfo(newParentPage.Path);

            // make the move
            var pageDiscInfo = _fileHelpers.GetPageDiscInfo(page.Path);

            if (pageDiscInfo.IsDirectory)
            {
                Directory.Move(pageDiscInfo.ResidesInDirectory,
                               Path.Combine(parentPageDiscInfo.ResidesInDirectory, targetName));
            }
            else
            {
                File.Move(pageDiscInfo.PageFilePath, parentPageDiscInfo.ResidesInDirectory + targetName + ".page");
            }

            // update the path
            page.Path = newParentPage.Path + @"/" + targetName;
            return SavePage(page);
        }


        private string GetUniqueDiscName(string desiredTargetPath, int attempt = 0)
        {
            var tryPath = desiredTargetPath;
            if (attempt > 0)
            {
                tryPath = Path.Combine(Path.GetDirectoryName(desiredTargetPath),
                                       Path.GetFileNameWithoutExtension(desiredTargetPath) + " (" + attempt + ")");
            }
            if (File.Exists(tryPath + ".page") || Directory.Exists(tryPath))
            {
                return GetUniqueDiscName(desiredTargetPath, attempt + 1);
            }
            return Path.GetFileNameWithoutExtension(tryPath);
        }

        private void ConvertToFolder(IPageStub pageStub)
        {
            var discInfo = _fileHelpers.GetPageDiscInfo(pageStub.Path);
            if (discInfo.IsDirectory)
                return;

            // create the folder
            var folderPath = _fileHelpers.ToDiscPath(pageStub.Path);
            Directory.CreateDirectory(folderPath);

            File.Move(discInfo.PageFilePath, Path.Combine(folderPath, _xmlStorageConfig.FolderIndexPageName + ".page"));
        }

        // TODO a good candidate for being exposed as an interface to allow security checks by other modules and similar
        public bool IsValidMoveTarget(IPage page, IPageStub newParentPage)
        {
            var targetParent = _fileHelpers.ToDiscPath(newParentPage.Path);
            if (Directory.Exists(targetParent))
            {
                return !File.Exists(targetParent + "\\" + page.Name + ".page")
                       &&
                       !Directory.Exists(targetParent + "\\" + page.Name);
            }
            return true;
        }

        public bool IsValidCopyTarget(IPage page, IPage newParentPage)
        {
            return IsValidMoveTarget(page, ToPageStub(newParentPage));
        }

        public bool IsValidPageName(string name)
        {
            return name.ToLower() != _xmlStorageConfig.FolderIndexPageName;
        }

        private IPage MovePageFile(IPage page, IPage newParentPage)
        {
            return MovePageFile(page, ToPageStub(newParentPage));
        }

        private IPage CopyPageFile(IPage page, IPage newParentPage)
        {
            throw new NotImplementedException();
            // TODO copy page file
            // check if target is same as source


            if (!IsValidCopyTarget(page, newParentPage))
                return null;

            // check if target is a folder already, otherwise turn it into a folder

            // check if source is a folder without child pages

            // Copy file
        }

        public bool CopyPage(IPage page, string targetParentPath)
        {
            var targetParentPage = GetPage(targetParentPath);

            if (targetParentPage != null)
                return CopyPage(page, targetParentPage);

            return false;
        }

        public bool CopyPage(IPage page, IPage targetParentPage)
        {
            var newPage = CopyPageFile(page, targetParentPage);
            if (newPage == null)
                return false;

            // update navigation file
            LookUpProvider.AddPageStub(ToPageStub(newPage), newPage.ParentPath);


            // update navigation cache
            throw new NotImplementedException();

            //return true;
        }

        public bool DeletePage(IPage page)
        {
            var oldPath = page.Path;

            page.Deleted = DateTime.Now;
            page.DeletedBy = GetCurrentUserId.CurrentUserId;
            page = SavePage(page);

            var movedPage = MovePageFile(page, GetTrashcanRoot(), true);
            if (movedPage != null)
            {
                LookUpProvider.RemovePageStub(oldPath);

                return true;
            }
            return false;
        }

        public IPageStub GetTrashcanRoot()
        {
            return ToPageStub(new Page("Trashcan")
                                  {
                                      Path = _xmlStorageConfig.BaseTrashcanPath
                                  });

        }

        public IEnumerable<IPageStub> GetTrashCanRootPageStubs()
        {
            return _fileReader.GetPageStubsFromDirectory(_fileHelpers.ToDiscPath(_xmlStorageConfig.BaseTrashcanPath));
        }

        private IPageStub ToPageStub(IPage page)
        {
            return new PageStub(page.Name, page.Path, page.Title, page.TemplatePath, page.Hidden);
        }

        #endregion

        #region Templates


        /// <summary>
        /// Loads and returns a template object.
        /// </summary>
        /// <param name="path">Relative path from templates root directory</param>
        /// <returns>Template</returns>
        public IPageTemplate GetTemplate(string path)
        {
            if (!path.ToLower().EndsWith(".template"))
                path += ".template";
            path = path.Replace(@"/", @"\");
            path = (path.StartsWith(@"\")) ? path.Substring(1) : path;

            path = Path.Combine(_xmlStorageConfig.BaseTemplatePath, path);

            if (!File.Exists(path))
                return null;

            // TODO cache this
            var newTemplate = _fileHelpers.LoadObjectFromDisc<PageTemplate>(path);

            newTemplate.FieldSettings.ResetIndices();
            return newTemplate;
        }


        public IPageTemplate SaveTemplate(IPageTemplate template)
        {
            if (string.IsNullOrEmpty(template.Path))
                template.Path = _fileHelpers.ToValidName(template.Name);
            var path = _xmlStorageConfig.BaseTemplatePath + template.Path.Replace("/", @"\");

            if (!path.ToLower().Contains(".template"))
                path = path + ".template";

            // make sure all parents exists as folders
            if (!File.Exists(path))
            {
                Directory.CreateDirectory(Path.GetDirectoryName(path));
            }

            return _fileHelpers.SaveToFile<PageTemplate>(template as PageTemplate, path);
        }



        public IEnumerable<IPageTemplate> GetTemplates()
        {
            var folder = new DirectoryInfo(Path.Combine(_xmlStorageConfig.BaseTemplatePath));

            return folder.GetFiles("*.template").Select(f => GetTemplate(f.Name));
        }

        #endregion
    }
}
